Stop forwarding caller-supplied bearers, and stop advertising the wikis' issuers - #518
Merged
Merged
Conversation
alistair3149
force-pushed
the
deprecate-bearer-passthrough
branch
from
July 30, 2026 15:00
47baeb2 to
7556077
Compare
alistair3149
marked this pull request as ready for review
July 30, 2026 15:08
alistair3149
force-pushed
the
deprecate-bearer-passthrough
branch
from
July 30, 2026 15:37
7556077 to
f118dbe
Compare
…is' issuers With the hosted proxy disabled, an Authorization header was forwarded to MediaWiki verbatim and the protected-resource document named the wikis' own authorization servers. Together those steered a client into minting a token at the wiki and presenting it here - the shape MCP forbids, since a server must not accept tokens that were not issued for it. Forwarding now requires MCP_ALLOW_BEARER_PASSTHROUGH=true, is deprecated, and warns at startup. Without it a request carrying a bearer is refused with 401 rather than served: ignoring the header would run the request anonymously or as a configured identity while the caller believed it was acting as itself, which is worse than refusing it. The refusal is deliberately unconditional and NOT keyed on whether a wiki sets oauth2ClientId. That option describes how this server runs browser sign-in, not whether the wiki accepts bearers - any wiki with Extension:OAuth does - so keying on it would keep forwarding tokens on exactly the documented public read-only and manual-token deployments. A test covers that shape. The advertisement is removed unconditionally, including under the opt-in. Only the hosted proxy makes this server an authorization server, so with the proxy off /.well-known/oauth-protected-resource now answers 404. The documented use for forwarding is callers that already hold a token, so they need no discovery, and restoring it would reinstate the steering this closes. Answering before the upstream fetches also means an unauthenticated request no longer costs one outbound metadata fetch per OAuth wiki. Because that document can now be absent, the 401 challenge only advertises resource_metadata when it exists; RFC 6750 admits a challenge without it. Otherwise every challenge pointed a client at a URL that 404s. The all-OAuth 401 challenge is gated on the opt-in: it asks a caller to supply a wiki token, and with no way to forward one the same condition is an operator misconfiguration instead. So the server now warns at startup when a wiki needs a signed-in user but neither hosted sign-in nor forwarding can provide one, and the tool-layer error names the operator action rather than asking for a token the transport would refuse. Tests that cover the forwarding shape now run under the flag, since the behaviour still exists there. The multi-wiki issuer listing is deleted rather than gated - that behaviour is gone. The fake authorization server gained a metadata-request counter so the no-fetch claim is actually asserted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
alistair3149
force-pushed
the
deprecate-bearer-passthrough
branch
from
July 30, 2026 16:07
f118dbe to
56f3942
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #504.
With the hosted OAuth proxy disabled, an
Authorizationheader was forwarded to MediaWiki verbatim, and/.well-known/oauth-protected-resourcenamed the wikis' own authorization servers. Together those steered a client into minting a token at the wiki and presenting it here — the shape MCP forbids, since a server must not accept tokens that were not issued for it.What changes
Forwarding requires
MCP_ALLOW_BEARER_PASSTHROUGH=true, is deprecated, and warns at startup. Without it a request carrying a bearer is refused401rather than served: ignoring the header would run the request anonymously or as a configured identity while the caller believed it was acting as itself.Nothing advertises the wikis' authorization servers any more. With the proxy off,
/.well-known/oauth-protected-resourceanswers404, andlist-wikisomits each wiki'sauthorizationServer— it named where to mint a token this server now refuses. The401challenge carriesresource_metadataonly when that document exists; otherwise it is a bareBearerchallenge, which RFC 6750 admits.A deployment that can no longer serve anything says so: the server warns at startup when a wiki requires a signed-in user and neither hosted sign-in nor forwarding can provide one. A
privatedefault wiki is stranded even with static credentials, because the challenge precedes credential resolution. The tool-layer error names the operator's action rather than asking for a token the transport would refuse.Two exported signatures tightened, in both cases because omitting the argument now means something dangerous rather than something useful:
buildProtectedResourcetakes a requiredauthorizationServers, andcreateOAuthProtectedResourceHandlera requiredgetProxyConfig(pass() => nullfor no proxy).What a reviewer should weigh
The refusal is deliberately not keyed on
oauth2ClientId. That option describes how this server runs browser sign-in, not whether the wiki accepts bearers — any wiki with Extension:OAuth does. Keying on it would keep forwarding tokens on exactly the documented public read-only and manual-token shapes. A test covers that configuration.The advertisement is removed even under the opt-in. Forwarding is documented for callers that already hold a token, so they need no discovery, and restoring it would reinstate the steering this closes.
Client impact is narrower than the diff
No client-negotiation surface is touched:
register.ts,cimd.ts,redirectPolicy.tsandasMetadata.tsare unchanged, so hosted-sign-in deployments behave identically — discovery, dynamic registration and CIMD all as before.UpstreamAsMetadatacarries noregistration_endpoint, so MediaWiki offers no dynamic client registration and a client that discovered a wiki's issuer could never self-register. The advertisement was never a zero-install path. The one real break is manual-token setup — the population this deprecation exists to migrate.Verification
Full suite 1,696 green; typecheck, lint (zero warnings) and format clean.
Confirmed end to end against a real MediaWiki 1.43.6 wiki, with a request tap between this server and the wiki recording the
Authorizationheader of everything we send upstream./.well-known/oauth-protected-resourceauthorization_serversnames only the proxy401challengeBearerresource_metadatalist-wikisauthorization serverauth_shapeanonymousbearer-passthroughoauth-proxyAnonymous reads return real page content and do reach the wiki in every column, so the "nothing reaches the wiki" cells are genuine negatives rather than a probe that stopped working. A
privatewiki that can be signed into by no route warns at startup and names the setting to change. The refusal was also exercised against a configuration with nooauth2ClientIdanywhere, the shape the gate described above would have missed.Each behaviour change is mutation-bound: reverting the refusal, restoring the advertisement, ungating the tokenless challenge, or making the challenge always advertise each fail a test.
Open
The deprecation says forwarding will be removed without naming a release.
src/runtime/authShape.tsreadsprocess.envthrough a default parameter rather than the explicit injectionevaluateBearerGuarduses. Threading an environment through the per-request call sites would be invasive for a cosmetic gain, so it is tested directly instead.